package com.lianqi.emcpframework.core.dict;

import com.alibaba.fastjson.JSONException;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.parser.DefaultJSONParser;
import com.alibaba.fastjson.parser.JSONLexer;
import com.alibaba.fastjson.parser.JSONToken;
import com.alibaba.fastjson.parser.deserializer.ObjectDeserializer;
import com.alibaba.fastjson.serializer.JSONSerializable;
import com.alibaba.fastjson.serializer.JSONSerializer;

import java.io.IOException;
import java.lang.reflect.Type;
import java.util.*;
import java.util.function.Predicate;
import java.util.function.Supplier;

/**
 * @program emcp-framework
 * @ClassName EnumDict
 * @description: 枚举类型字典
 * @author: sky
 * @create: 2018/11/22 16:16
 */
public interface EnumDict<V> extends JSONSerializable {

    static final int MAX_ITEM_NUM = 64;

    boolean WITH_DSERIALIZE = !Boolean.getBoolean("");

    /**
     * 枚举选项的值,通常由字母或者数字组成,并且在同一个枚举中值唯一;对应数据库中的值通常也为此值
     *
     * @return 枚举的值
     * @see DictionaryItem#getValue()
     */
    V getValue();

    /**
     * 枚举字典选项的文本,通常为中文
     *
     * @return 枚举的文本
     * @see DictionaryItem#getText()
     */
    String getText();

    /**
     * {@link Enum#ordinal()}
     *
     * @return 枚举序号, 如果枚举顺序改变, 此值将被变动
     */
    int ordinal();

    /**
     * 枚举序号
     * @return 枚举序号
     */
    default long index(){
        return ordinal();
    }

    /**
     * 获取掩码   64位对应index位为1
     * @return
     */
    default long getMask(){
        return 1L << index();
    }

    /**
     * 对比是否和value相等,对比地址,值,value转为string忽略大小写对比,text忽略大小写对比
     *
     * @param object value
     * @return 是否相等
     */
    default boolean eq(Object object){
        if (object == null){
            return false;
        }
        if (object instanceof Object[]){
            object = Arrays.asList(object);
        }
        if (object instanceof Collection){
            return (((Collection) object).stream().anyMatch(this::eq));
        }
        if (object instanceof Map){
            object = (((Map) object).getOrDefault("value",((Map)object).get("text")));
        }
        return this == object || getValue() == object
                || getValue().equals(object)
                || (object instanceof Number ? in(((Number)object).longValue()):false)
                || String.valueOf(getValue()).equalsIgnoreCase(String.valueOf(object))
                || object.equals(getMask())
                || getText().equalsIgnoreCase(String.valueOf(object));
    }

    /**
     * 根据掩码判断是否存在枚举中
     * @param mask 掩码
     * @return true/false
     */
   default boolean in(long mask){
        return (mask & getMask()) != 0;
    }

    /**
     * 枚举选项的描述,对一个选项进行详细的描述有时候是必要的.默认值为{@link this#getText()}
     *
     * @return 描述
     */
    default String getComments(){
        return getText();
    }

    /**
     * 从指定的枚举类中查找想要的枚举,并返回一个{@link Optional},如果未找到,则返回一个{@link Optional#empty()}
     *
     * @param type      实现了{@link EnumDict}的枚举类
     * @param predicate 判断逻辑
     * @param <T>       枚举类型
     * @return 查找到的结果
     */
    static <T extends Enum & EnumDict> Optional<T> find(Class<T> type, Predicate<T> predicate){
        if (type.isEnum()){
            for (T enumDict : type.getEnumConstants()){
                if (predicate.test(enumDict)){
                    return Optional.of(enumDict);
                }
            }
        }
        return Optional.empty();
    }

    /**
     * 根据枚举的{@link EnumDict#getValue()}来查找.
     *
     * @see this#find(Class, Predicate)
     */
    static <T extends Enum & EnumDict<?>> Optional<T> findByValue(Class<T> type, Object value){
        return find(type,e -> e.getValue() == value || e.getValue().equals(value)
        ||String.valueOf(e.getValue()).equalsIgnoreCase(String.valueOf(value)));
    }

    /**
     * 根据枚举的{@link EnumDict#getText()} 来查找.
     *
     * @see this#find(Class, Predicate)
     */
    static <T extends Enum & EnumDict> Optional<T> findByText(Class<T> type,String text){
        return find(type,e -> e.getText().equalsIgnoreCase(text));
    }

    /**
     * 根据枚举的{@link EnumDict#getValue()},{@link EnumDict#getText()}来查找.
     *
     * @see this#find(Class, Predicate)
     */
    static <T extends Enum & EnumDict> Optional<T> find(Class<T> type,Object target){
        return find(type,v -> v.eq(target));
    }

    /**
     * 通过掩码获取枚举中的元素
     * @param allOptions
     * @param mask 掩码
     * @param <T> 泛型
     * @return 匹配掩码的元素集合
     */
    static <T extends Enum & EnumDict> List<T> findByMask(List<T> allOptions,long mask){
        if (allOptions.size() >= MAX_ITEM_NUM){
            throw new UnsupportedOperationException("不支持选项超过"+MAX_ITEM_NUM +"个的数据字典！");
        }
        List<T> list = new ArrayList<>();

        for (T option : allOptions){
            if (option.in(mask)){
                list.add(option);
            }
        }
        return list;
    }

    /**
     * 通过掩码获取枚举中的元素
     * @param supplier
     * @param mask
     * @param <T>
     * @return
     */
    static <T extends Enum & EnumDict> List<T> findByMask(Supplier<List<T>> supplier,long mask){
        return findByMask(supplier.get(),mask);
    }

    /**
     * 通过掩码获取枚举中的元素
     * @see this#findByMask(List, long)
     * @param type
     * @param mask
     * @param <T>
     * @return
     */
    static <T extends Enum & EnumDict> List<T> findByMask(Class<T> type,long mask){
        return findByMask(Arrays.asList(type.getEnumConstants()),mask);
    }

    /**
     * 生成掩码
     * @param obj
     * @param <T>
     * @return
     */
    @SafeVarargs
    static <T extends EnumDict> long toMask(T... obj){
        if (obj == null){
            return 0L;
        }
        long mask = 0L;
        for (T o : obj){
            mask |= o.getMask();
        }
        return mask;
    }

    /**
     * 判断掩码是否包含所有的对象
     * @param mask
     * @param obj
     * @param <T>
     * @return
     */
    @SafeVarargs
    static <T extends EnumDict> boolean maskIn(long mask,T... obj){
        long value = toMask(obj);
        return (mask & value) == value;
    }

    /**
     * 判断掩码中是否包含某个对象
     * @param mask
     * @param obj
     * @param <T>
     * @return
     */
    @SafeVarargs
    static <T extends EnumDict> boolean maskInAny(long mask,T... obj){
        long value = toMask(obj);
        return (mask & value) != 0;
    }

    /**
     * 默认在序列化为json时,默认会以对象方式写出枚举,可通过系统环境变量 <code>hsweb.enum.dict.disableWriteJSONObject</code>关闭默认设置。
     * 比如: java -jar -Dhsweb.enum.dict.disableWriteJSONObject=true
     */
    boolean DEFAULT_WRITE_JSON_OBJECT = !Boolean.getBoolean("emcp.enum.dict.disableWriteJSONObject");

    /**
     * @return 是否在序列化为json的时候, 将枚举以对象方式序列化
     * @see this#DEFAULT_WRITE_JSON_OBJECT
     */
    default boolean isWriteJSONObjectEnabled() {
        return DEFAULT_WRITE_JSON_OBJECT;
    }

    /**
     * 当{@link this#isWriteJSONObjectEnabled()}返回true时,在序列化为json的时候,会写出此方法返回的对象
     *
     * @return 最终序列化的值
     * @see this#isWriteJSONObjectEnabled()
     */
    default Object getWriteJSONObject() {
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("value", getValue());
        jsonObject.put("text", getText());
        jsonObject.put("index", index());
        jsonObject.put("mask", getMask());
        return jsonObject;
    }



    @Override
    default void write(JSONSerializer jsonSerializer, Object o, Type type, int i)throws IOException {
        if (isWriteJSONObjectEnabled()) {
            jsonSerializer.write(getWriteJSONObject());
        } else {
            jsonSerializer.write(getValue());
        }
    }

    class EnumDictJSONDeserializer implements ObjectDeserializer{

        @Override
        public <T> T deserialze(DefaultJSONParser parser, Type type, Object fieldName) {
            try {
            Object value;
            final JSONLexer lexer = parser.lexer;
            final int token = lexer.token();
            if (token == JSONToken.LITERAL_INT){
                int intValue = lexer.intValue();
                lexer.nextToken(JSONToken.COMMA);
                return (T) EnumDict.find((Class)type,intValue);
            }else if (token == JSONToken.LITERAL_STRING){
                String name = lexer.stringVal();
                lexer.nextToken(JSONToken.COMMA);
                if (name.length() == 0){
                    return (T) null;
                }
                return (T) EnumDict.find((Class)type,name).orElse(null);
            }else if (token == JSONToken.NULL){
                lexer.nextToken(JSONToken.COMMA);
                return null;
            }else {
                value = parser.parse();
                if (value instanceof Map){
                    return (T) EnumDict.find((Class)type,((Map)value).get("value"))
                            .orElseGet(() -> EnumDict.find((Class)type,((Map)value).get("text")).orElse(null));
                }
            }
            throw new JSONException("parse enum "+type +" error,value : "+ value);
            }catch (JSONException e){
                throw e;
            }catch (Exception e){
                throw new JSONException(e.getMessage(),e);
            }

        }

        @Override
        public int getFastMatchToken() {
            return JSONToken.LITERAL_STRING;
        }
    }
}
